using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.CodeDom;
using SharpMud.Server.Systems.Scripting.Processing.Source;

namespace SharpMud.Server.Systems.Scripting.Processing
{
    internal sealed partial class ScriptCompileContext : IScriptCompileContext
    {
        public const bool FilterErroneousScripts_ByDefault = true;

        private List<IScriptSourceHandle> _Scripts;
        private List<KeyValuePair<IScriptSourceHandle,IScriptCompileResults>> _LastCompileResults;
        private List<string> _AssemblyReferences;
        bool _IncludeDebugInfo;
        bool _IsDirty;

        IScriptManager _ScriptManager;

        private ScriptCompileContext()
        {
            _Scripts = new List<IScriptSourceHandle>();
            _LastCompileResults = new List<KeyValuePair<IScriptSourceHandle, IScriptCompileResults>>();
            _CompileSyncSink = new object();
        }
        internal ScriptCompileContext(IScriptManager scriptManager)
            :this()
        {
            this._ScriptManager = scriptManager;
        }

        private CompilerParameters GenerateCompilerParameters()
        {
            CompilerParameters cp = new CompilerParameters();
            cp.GenerateExecutable = false;
            cp.GenerateInMemory = true;
            cp.ReferencedAssemblies.AddRange((this as IScriptCompileContext).GetAssemblyReferences());
            return cp;
        }

        #region IScriptCompileContext Members

        public bool IncludeDebugInfo
        {
            get { return _IncludeDebugInfo; }
            set
            {
                if (value != _IncludeDebugInfo)
                {
                    _IncludeDebugInfo = value;
                    IsDirty = true;
                }
            }
        }

        bool IScriptCompileContext.IncludeDebugInfo
        {
            get { return _IncludeDebugInfo; }
        }


        IScriptSourceHandle[] IScriptCompileContext.GetScripts()
        {
            return _Scripts.ToArray();
        }

        int IScriptCompileContext.ScriptCount
        {
            get { return _Scripts.Count; }
        }

        void IScriptCompileContext.AddScript(IScriptSourceHandle scriptSourceHandle)
        {
            if (!this._Scripts.Contains(scriptSourceHandle))
            {
                this._Scripts.Add(scriptSourceHandle);
                this.IsDirty = true;
                if (this.ScriptAdded != null) this.ScriptAdded(this, new ScriptSourceMovedEventArgs(scriptSourceHandle));
            }
        }

        void IScriptCompileContext.RemoveScript(IScriptSourceHandle scriptSourceHandle)
        {
            if (this._Scripts.Contains(scriptSourceHandle))
            {
                this._Scripts.Remove(scriptSourceHandle);
                this.IsDirty = true;
                if (this.ScriptRemoved != null) this.ScriptRemoved(this, new ScriptSourceMovedEventArgs(scriptSourceHandle));
            }
        }

        void IScriptCompileContext.RemoveScriptAt(int index)
        {
            if (index < this._Scripts.Count)
            {
                (this as IScriptCompileContext).RemoveScript(this._Scripts[index]);
                return;
            }
            else
                throw new IndexOutOfRangeException();
        }

        System.Type[] IScriptCompileContext.GetLanguagesUsed()
        {
            List<System.Type> compilerLanguageTypes = new List<Type>();
            foreach (IScriptSourceHandle script in this._Scripts)
            {
                if (script.PreferredLanguage != null)
                {
                    if (!compilerLanguageTypes.Contains(script.PreferredLanguage))
                        compilerLanguageTypes.Add(script.PreferredLanguage);
                }
            }
            return compilerLanguageTypes.ToArray();
        }

        public bool ContainsScript(IScriptSourceHandle scriptSourceHandle)
        {
            return _Scripts.Contains(scriptSourceHandle);
        }

        string[] IScriptCompileContext.GetAssemblyReferences()
        {
            return _AssemblyReferences.ToArray();
        }

        int IScriptCompileContext.AssemblyReferenceCount
        {
            get { return _AssemblyReferences.Count; }
        }

        void IScriptCompileContext.AddAssemblyReference(string assemblyReference)
        {
            if (!this.ContainsAssemblyReference(assemblyReference))
            {
                this._AssemblyReferences.Add(assemblyReference);
                this.IsDirty = true;
            }
        }

        void IScriptCompileContext.RemoveAssemblyReference(string assemblyReference)
        {
            if (this.ContainsAssemblyReference(assemblyReference))
            {
                this._AssemblyReferences.Remove(assemblyReference);
                this.IsDirty = true;
            }
        }

        void IScriptCompileContext.RemoveAssemblyReferenceAt(int index)
        {
            if (index < this._AssemblyReferences.Count)
            {
                this._AssemblyReferences.RemoveAt(index);
                this.IsDirty = true;
            }
            else
                throw new IndexOutOfRangeException();
        }

        public bool ContainsAssemblyReference(string assemblyReference)
        {
            foreach (string as1 in this._AssemblyReferences)
            {
                if (StringComparer.InvariantCultureIgnoreCase.Compare(as1, assemblyReference) == 0)
                {
                    return true;
                }
            }
            return false;
        }

        public bool IsDirty
        {
            get { return _IsDirty; }
            private set
            {
                if (value == true && value != _IsDirty)
                {
                    _IsDirty = true;
                    if (this.ContextDirtied != null)
                        this.ContextDirtied(this, EventArgs.Empty);
                }
                else if (value == false && value != _IsDirty)
                {
                    _IsDirty = false;
                    if (this.ContextSettled != null)
                        this.ContextSettled(this, EventArgs.Empty);
                }
            }
        }

        public void Update()
        {
            KeyValuePair<IScriptSourceHandle, IScriptCompileResults>[] compileResults;
            compileResults = this.CompileScripts(true);
            foreach (KeyValuePair<IScriptSourceHandle, IScriptCompileResults> newCompileResult in compileResults)
            {
                lock (this._LastCompileResults)
                {
                    Nullable<KeyValuePair<IScriptSourceHandle, IScriptCompileResults>> oldResultToRemove = null;
                    foreach (KeyValuePair<IScriptSourceHandle, IScriptCompileResults> oldCompileResult in this._LastCompileResults)
                    {
                        if (newCompileResult.Key == oldCompileResult.Key)
                            oldResultToRemove = oldCompileResult;
                    }
                    if(oldResultToRemove != null)
                        this._LastCompileResults.Remove(oldResultToRemove.Value);
                }
                this._LastCompileResults.Add(newCompileResult);
                if (this.ScriptCompileAttempted != null)
                    this.ScriptCompileAttempted(this, new ScriptCompiledEventArgs(newCompileResult.Key, newCompileResult.Value));
            }
            this.IsDirty = false;
        }

        public event System.EventHandler ContextDirtied;
        public event System.EventHandler ContextSettled;

        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            this._CompileSyncSink = null;

            this._AssemblyReferences.Clear(); this._AssemblyReferences = null;

            foreach (IScriptSourceHandle ssh in this._Scripts)
            {
                ssh.Dispose();
            }
            this._Scripts.Clear(); this._Scripts = null;
            //foreach (ICompiledScriptHandle csh in this._CompiledScripts)
            //{
            //    csh.Dispose();
            //}
            //this._CompiledScripts.Clear(); this._CompiledScripts = null;

            if (this.Disposed != null) this.Disposed(this, EventArgs.Empty);            
        }
        #endregion

        #region IScriptCompileContext Members


        public IScriptCompileResults GetLastCompileResults(IScriptSourceHandle scriptSource)
        {
            foreach (KeyValuePair<IScriptSourceHandle,IScriptCompileResults> kvp in this._LastCompileResults)
            {
                if (kvp.Key == scriptSource)
                    return kvp.Value;
            }
            return null;
        }

        public event ScriptSourceMovedEventHandler ScriptAdded;

        public event ScriptSourceMovedEventHandler ScriptRemoved;

        public event EventHandler Disposed;

        public event ScriptCompiledEventHandler ScriptCompileAttempted;

        #endregion
    }
}